﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using xplang.run;
using xplang.errors;
using xplang.contexts;
using System.Diagnostics;
using xplang.rtl;
using xplang.rtl.operators;

namespace xplang.abstractions
{

  [DebuggerDisplay("{refname()}")]
  public abstract class ClassRef
  {

    public abstract ClassDecl getClassDecl();
    public abstract ClassRef[] getGenericMappings();

    public abstract String refname();

    public object invoke(ClassMethod classMethod, Value[] value)
    {
      throw new NotImplementedException();
    }

    public abstract bool instanciable();
    public Value instanciate()
    {
      return new Value(this, getClassDecl().newInstance());
    }

    public MethodImplementation findMethodImplementation(ClassMethod method)
    {
      MethodImplementation imp;
      ClassDecl decl = getClassDecl();
      if (!method.implementations.TryGetValue(decl, out imp))
      {
        int n = decl.inheritance.Length;
        for (int i = 0; i < n; i++)
        {
          imp = decl.inheritance[i].findMethodImplementation(method);
          if (imp != null)
            return imp;
        }
      }
      return imp;
    }

    public ClassMethod attribMethod(ClassRef pDef, ClassRef[] mappings)
    {
      return getClassDecl().
        findMethod(EquatableClass.opAttrib, new ClassRef[] { pDef }, mappings, true);
    }

    public abstract bool supports(ClassRef pDef, ClassRef[] genericMappings);
  }

  /**
   * Faz referência direta classe
   * ex. Integer x 
   *     A variável x é do tipo Integer
   */

  public class ClassRealRef : ClassRef
  {

    public ClassRealRef(ClassDecl decl, ClassRef[] genericMappings)
    {
      this.decl = decl;
      this.genericMappings = genericMappings;
      if (decl.genericParams.Length != (genericMappings == null ? 0 : genericMappings.Length))
        throw new GenericParamCount();
    }

    public readonly ClassDecl decl;
    public readonly ClassRef[] genericMappings;

    public override String refname()
    {
      StringBuilder s = new StringBuilder(decl.fullname());
      int n = genericMappings == null ? 0 : genericMappings.Length;
      for (int i = 0; i < n; i++)
      {
        if (i == 0)
          s.Append('<');
        else
          s.Append(',');
        s.Append(genericMappings[i].refname());
      }
      if (n > 0)
        s.Append('>');
      return s.ToString();
    }

    public override ClassDecl getClassDecl()
    {
      return decl;
    }

    public override ClassRef[] getGenericMappings()
    {
      return genericMappings;
    }

    public override bool supports(ClassRef pDef, ClassRef[] genericMappings)
    {
      if (pDef is GenericParamRef)
      {
        ClassRef b = ((pDef as GenericParamRef).paramDecl.baseClass);
        if (b != null)
          return supports(b, null);
        return genericMappings[0].supports(pDef, null);
      }
      return attribMethod(pDef, genericMappings) != null;
    }

    public override bool instanciable()
    {
      throw new NotImplementedException();
    }

    public bool isRef(ClassRef[] genericParams)
    {
      if (genericParams == null)
        return decl.genericParams.Length == 0;
      int n = genericParams.Length;
      if (n != decl.genericParams.Length)
        return false;

      for (int i = 0; i < n; i++)
      {
        GenericParamDecl gd = decl.genericParams[i];
        if (genericMappings[i].getClassDecl() != genericParams[i].getClassDecl())
          return false;
        if (gd.baseClass != null)
          if (gd.baseClass != genericParams[i])
            //if (gd.baseClass.attribMethod(genericParams[i], null) == null)
            return false;

      }
      return true;
    }
  }

  /**
  * Faz referência a um GenericParam de uma classe
  * ex. class CLASSE<T> { T x }
  *         CLASSE<T> é ClassDef
  *         A variável x é do tipo T
  *     CLASSE<Integer> x
  *         CLASSE<Integer> é ClassRef
  *         A variável x.x é do tipo Integer
  */

  public class GenericParamDecl
  {
    public GenericParamDecl(String name, ClassRef baseClass)
    {
      this.name = name;
      this.baseClass = baseClass;
    }

    public readonly String name;
    public readonly ClassRef baseClass;

    private GenericParamRef thisref;
    public GenericParamRef getRef()
    {
      if (thisref == null)
        thisref = new GenericParamRef(this);
      return thisref;
    }
  }

  public class GenericParamRef : ClassRef
  {

    public GenericParamRef(GenericParamDecl paramDecl)
    {
      this.paramDecl = paramDecl;
    }

    public readonly GenericParamDecl paramDecl;

    public override ClassDecl getClassDecl()
    {
      return paramDecl.baseClass.getClassDecl();
    }

    public override ClassRef[] getGenericMappings()
    {
      return null;
    }

    public override String refname()
    {
      return paramDecl.name;
    }

    public override bool instanciable()
    {
      throw new NotImplementedException();
    }

    public override bool supports(ClassRef pDef, ClassRef[] genericMappings)
    {
      if (genericMappings == null)
        return true;
      return false;
    }
  }
}
